package com.ruoyi.sjl.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableMap;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.sjl.domain.Rescue;
import com.ruoyi.sjl.handler.RescueWebSocketHandler;
import com.ruoyi.sjl.mapper.RescueMapper;
import com.ruoyi.sjl.service.IRescueOrderService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.stereotype.Service;
import com.ruoyi.sjl.mapper.SjlMerchantMapper;
import com.ruoyi.sjl.domain.Merchant;
import com.ruoyi.sjl.service.IMerchantService;

/**
 * 商户Service业务层处理
 * 
 * @author ruoyi
 * @date 2025-11-13
 */
@Service
@Slf4j
public class MerchantServiceImpl implements IMerchantService 
{
    @Autowired
    private SjlMerchantMapper sjlMerchantMapper;
    @Autowired
    private RescueMapper rescueMapper;
    @Autowired
    private IRescueOrderService rescueOrderService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RescueWebSocketHandler rescueWebSocketHandler;
    private static final String RESCUE_ACCEPTED_MERCHANT_KEY_PREFIX = "rescue:accepted:merchant:";
    private static final String GEO_KEY = "merchant:geo";
    private static final String RESCUE_STATUS_KEY_PREFIX = "rescue:status:";
    private static final String RESCUE_LOCK_KEY_PREFIX = "rescue:lock:";

    /**
     * 查询商户
     * 
     * @param id 商户主键
     * @return 商户
     */
    @Override
    public Merchant selectMerchantById(Long id)
    {
        return sjlMerchantMapper.selectMerchantById(id);
    }

    /**
     * 查询商户列表
     * 
     * @param merchant 商户
     * @return 商户
     */
    @Override
    public List<Merchant> selectMerchantList(Merchant merchant)
    {
        return sjlMerchantMapper.selectMerchantList(merchant);
    }

    /**
     * 新增商户
     * 
     * @param merchant 商户
     * @return 结果
     */
    @Override
    public int insertMerchant(Merchant merchant)
    {
        merchant.setCreateTime(DateUtils.getNowDate());
        return sjlMerchantMapper.insertMerchant(merchant);
    }

    /**
     * 修改商户
     * 
     * @param merchant 商户
     * @return 结果
     */
    @Override
    public int updateMerchant(Merchant merchant)
    {
        merchant.setUpdateTime(DateUtils.getNowDate());
        return sjlMerchantMapper.updateMerchant(merchant);
    }

    /**
     * 批量删除商户
     * 
     * @param ids 需要删除的商户主键
     * @return 结果
     */
    @Override
    public int deleteMerchantByIds(Long[] ids)
    {
        return sjlMerchantMapper.deleteMerchantByIds(ids);
    }

    /**
     * 删除商户信息
     * 
     * @param id 商户主键
     * @return 结果
     */
    @Override
    public int deleteMerchantById(Long id)
    {
        return sjlMerchantMapper.deleteMerchantById(id);
    }

    @Override
    public AjaxResult updateLatAndLau(Merchant merchant){
        System.out.println("member 写入 Redis: [" + merchant.getId() + "]");
        log.info("====== RAW ID ====== {}", merchant.getId());
        log.info("====== STRING ID ====== {}", merchant.getId().toString());
        log.info("====== CLEANED ID ====== {}", merchant.getId().toString().replace("\"", ""));
        log.info("====== longitude ====== {}", merchant.getLongitude());
        log.info("====== latitude ====== {}", merchant.getLatitude());

        sjlMerchantMapper.updateMerchant(merchant);
        // 判空处理
        if (merchant.getLongitude() == null || merchant.getLatitude() == null) {
            return AjaxResult.error("经纬度信息不能为空");
        }
        try {
            // 将String类型的经纬度转换为double
            double longitude = Double.parseDouble(merchant.getLongitude());
            double latitude = Double.parseDouble(merchant.getLatitude());
            String member = String.valueOf(merchant.getId()).replace("\"", "");
            redisService.geoAdd(GEO_KEY, longitude, latitude, member);
            return AjaxResult.success();
        } catch (NumberFormatException e) {
            return AjaxResult.error("经纬度格式不正确");
        }
    }

    @Override
    public AjaxResult grabOrder(String rescueId, String merchantId){
        //1. 参数校验
        if (rescueId == null || merchantId == null){
            log.warn("商家抢单失败：救援ID={}, 商家ID={}，参数为空", rescueId, merchantId);
            return AjaxResult.error("救援ID和商家ID不能为空");
        }
        // 2. 订单状态校验
        // 2.1 数据库校验：仅验证订单是否存在
        Rescue rescue = rescueMapper.selectRescueById(Integer.parseInt(rescueId));
        if (rescue == null) {
            log.warn("商家抢单失败：救援订单{}不存在", rescueId);
            return AjaxResult.error("订单不存在");
        }
        // 2.2 Redis校验：优先查订单状态
        String statusKey = RESCUE_STATUS_KEY_PREFIX + rescueId;
        Integer redisStatus  = redisService.getCacheObject(statusKey);
        Integer finalStatus;
        // 2.3 状态兜底：Redis无状态/异常时，以数据库状态为准
        if (redisStatus == null) {
            log.warn("订单{}：Redis状态为空，以数据库状态为准", rescueId);
            finalStatus = rescue.getStatus();
            // 顺便回写Redis（修复缓存穿透）
            redisService.setCacheObject(statusKey, finalStatus, 24L, TimeUnit.HOURS);
        }
        else {
            // Redis有状态：先对比数据库，避免一致性问题
            if (!redisStatus.equals(rescue.getStatus())) {
                // 异常情况：Redis与数据库状态不一致，以数据库为准并同步Redis
                log.error("订单{}：状态不一致（Redis={}, DB={}），强制同步", rescueId, redisStatus, rescue.getStatus());
                finalStatus = rescue.getStatus();
                redisService.setCacheObject(statusKey, finalStatus, 24L, TimeUnit.HOURS);
            } else {
                finalStatus = redisStatus;
            }
        }

        // 2.4 最终状态判断（仅待接单=0可抢）
        if (finalStatus != 0) {
            String msg = finalStatus == 1 ? "订单已被其他商家抢单" : "订单已取消，无法抢单";
            log.warn("商家{}抢单失败：{}（订单{}状态={}）", merchantId, msg, rescueId, finalStatus);
            return AjaxResult.error(msg);
        }
        // 3. 订单抢单逻辑
        String lockKey = RESCUE_LOCK_KEY_PREFIX + rescueId;
        RLock lock = redissonClient.getLock(lockKey); // 获取 Redisson 锁实例
        boolean lockAcquired = false;
        try{
            // 尝试获取锁：waitTime=3秒（最多等3秒），leaseTime=-1（自动续命，直到释放）
            lockAcquired = lock.tryLock(3, -1, TimeUnit.SECONDS);
            if (!lockAcquired) {
                log.warn("商家{}抢单失败：订单{}抢单冲突，请重试", merchantId, rescueId);
                return AjaxResult.error("抢单太火爆，请重试");
            }
            // 4. Redis状态最终二次校验（优化点2：补充Redis为空的兜底）防止A商家继续下面流程改完释放后 B商家还要走一遍下边
            redisStatus = redisService.getCacheObject(statusKey);
            // 这里加一层：如果Redis状态为空，直接用数据库的finalStatus判断（避免极端情况）
            if (redisStatus == null || redisStatus != 0) {
                String msg = redisStatus == null ? "订单状态异常" : "订单已被其他商家抢单";
                log.warn("商家{}抢单失败：订单{}Redis状态={}", merchantId, rescueId, redisStatus);
                return AjaxResult.error(msg);
            }
            //5最终校验 防止商家恶意抢单  广播异常  redis缓存异常
            String notifiedMerchantsKey = "rescue:notified:" + rescueId;
            Set<String> notifiedMerchants = redisService.getCacheSet(notifiedMerchantsKey);
            if (notifiedMerchants == null || !notifiedMerchants.contains(merchantId)) {
                log.warn("商家{}抢单失败：未收到订单{}的救援通知，无抢单权限", merchantId, rescueId);
                return AjaxResult.error("无此订单的抢单权限");
            }
            //6 更新订单状态
            rescue.setStatus(1);
            rescueMapper.updateRescue(rescue); // 先写数据库
            int updateCount = rescueMapper.updateRescue(rescue);
            if (updateCount <= 0) {
                log.error("商家{}抢单失败：订单{}数据库状态更新失败", merchantId, rescueId);
                redisService.setCacheObject(statusKey, 0, 24L, TimeUnit.HOURS);
                redisService.deleteObject(RESCUE_ACCEPTED_MERCHANT_KEY_PREFIX + rescueId);
                return AjaxResult.error("抢单失败，请重试");
            }
            redisService.setCacheObject(statusKey, 1, 24L, TimeUnit.HOURS);
            redisService.setCacheObject(RESCUE_ACCEPTED_MERCHANT_KEY_PREFIX + rescueId, merchantId, 24L, TimeUnit.HOURS);

            // 7. 实时通知相关方（不变）
            rescueWebSocketHandler.notifyUser(rescue.getUid().toString(),"rescue_success",ImmutableMap.of(
                    "rescueId", rescueId,
                        "merchantId", merchantId,
                    "message", "商家正在赶来！"
            )
                    );
            rescueWebSocketHandler.notifyMerchant(merchantId,"task_success", ImmutableMap.of(
                    "rescueId", rescueId,
                    "status", "已抢单",
                    "message", "抢单成功！请尽快赶往救援！"
            ));
            Set<String> otherMerchants = notifiedMerchants.stream()
                    .filter(mid -> !mid.equals(merchantId))
                    .collect(Collectors.toSet());
            if (!otherMerchants.isEmpty()) {
                List<RedisGeoCommands.GeoLocation<String>> merchantList = otherMerchants.stream()
                        .map(merchantIdStr -> new RedisGeoCommands.GeoLocation<>(merchantIdStr, null))
                        .collect(Collectors.toList());
                rescueWebSocketHandler.broadcastMerchantsToNearby(merchantList, "task_fail",ImmutableMap.of(
                        "rescueId", rescueId,
                        "message", "订单已被其他商家抢单，请重新选择",
                        "status", "抢单失败"
                ));
            }
            log.info("商家{}抢单成功：订单{}状态更新为已接单", merchantId, rescueId);
            return AjaxResult.success("抢单成功，已为您锁定订单");
        } catch (InterruptedException e) {
            log.error("商家{}抢单被中断：订单{}", merchantId, rescueId, e);
            Thread.currentThread().interrupt();
            return AjaxResult.error("抢单异常，请重试");
        } catch (Exception e) {
            log.error("商家{}抢单异常：订单{}", merchantId, rescueId, e);
            return AjaxResult.error("抢单异常，请联系客服");
        } finally {
            // 8. 释放锁（不变）
            if (lockAcquired && lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("商家{}释放订单{}的锁", merchantId, rescueId);
            }
        }

    }
}
